#include "LowLevelLayer.h"
#include <iostream>
using std::cerr;


LowLevelLayer::LowLevelLayer(string& fileName, unsigned int blockSize, unsigned int diskSize, unsigned int iNodesCount):
_da(NULL),
_superBlock(NULL)
{
	_da = new DiskAbstraction(fileName,blockSize,diskSize);
	if (_da==NULL){
		throw Exception("Could not create Disk Abstraction layer for low level layer. Exiting...");
	}
	_superBlock = new SuperBlockData(_da,iNodesCount);
	if (_superBlock==NULL){
		delete _da;
		throw Exception("Could not create super block for low level layer. Exiting...");
	}
	if (_da->isNew()){//allocated root inode
		INodeId rootInodeId(allocateInode());
		_superBlock->set(SuperBlockData::ROOT_INODE,rootInodeId);
		setInodeType(rootInodeId,DIRECTORY_TYPE);
	}
}

LowLevelLayer::~LowLevelLayer(void)
{
//	delete _superBlock;
//	delete _da;
}

//INode control
INodeId LowLevelLayer::allocateInode(){
	INodeId res(0);
	unsigned int freeInodesCount(_superBlock->get(SuperBlockData::FREE_INODES_COUNT));
	if(freeInodesCount<=0){
		cerr<<LowLevelLayer::NO_FREE_INODES;
		throw Exception("No free nodes to allocate");
	}
	unsigned int adressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	unsigned int firstFreeInodesBlock(_superBlock->get(SuperBlockData::FIRST_FREE_INODES_BLOCK));
	unsigned int firstFreeInodeOffset(_superBlock->get(SuperBlockData::FIRST_FREE_INODE_OFFSET));
	//remove inode id from list
	_da->seekAndRead(firstFreeInodesBlock,sizeof(unsigned int),firstFreeInodeOffset,&res,sizeof(unsigned int));
	unsigned int temp=0;
	_da->seekAndWrite(firstFreeInodesBlock,sizeof(unsigned int),firstFreeInodeOffset,&temp,sizeof(unsigned int));//TODO just for debug
	//set new offset
	unsigned int newOffset((firstFreeInodeOffset+1)%adressesPerBlock);
	_superBlock->set(SuperBlockData::FIRST_FREE_INODE_OFFSET,newOffset);
	//set new block (if needed)
	if (newOffset==0){
		unsigned int newBlock(0);
		_da->seekAndRead(firstFreeInodesBlock,sizeof(unsigned int),firstFreeInodeOffset+1,&newBlock,sizeof(unsigned int));
		_superBlock->set(SuperBlockData::FIRST_FREE_INODES_BLOCK,newBlock);
	}
	_superBlock->set(SuperBlockData::FREE_INODES_COUNT,freeInodesCount-1);
	initNewInode(res);
	return res-3;
}

/*frees the specified i-node and also frees all the data blocks pointed to by it (directly or indirectly).*/
void LowLevelLayer::freeInode(INodeId iNodeId){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	iNodeId=iNodeId+3;
	unsigned int adressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	freeINode(iNodeId);
	unsigned int lastFreeInodesBlockId(_superBlock->get(SuperBlockData::LAST_FREE_INODS_BLOCK));
	unsigned int lastFreeInodesOffset(_superBlock->get(SuperBlockData::LAST_FREE_INODE_OFFSET));
	//calculate correct block and offset to add
	unsigned int newOffset((lastFreeInodesOffset+1)%adressesPerBlock);
	_superBlock->set(SuperBlockData::LAST_FREE_INODE_OFFSET,newOffset);
	if (newOffset==0){
		_da->seekAndRead(lastFreeInodesBlockId,sizeof(unsigned int),lastFreeInodesOffset+1,&lastFreeInodesBlockId,sizeof(unsigned int));
		_superBlock->set(SuperBlockData::LAST_FREE_BLOCKS_BLOCK,lastFreeInodesBlockId);
	}
	//write just freed inode id to list
	_da->seekAndWrite(lastFreeInodesBlockId,sizeof(unsigned int),newOffset,&iNodeId,sizeof(unsigned int));
	unsigned int freeInodesCount(_superBlock->get(SuperBlockData::FREE_INODES_COUNT));
	_superBlock->set(SuperBlockData::FREE_BLOCKS_COUNT,freeInodesCount+1);
	/*//erase data from block
	char* zerroBlock = new char[_superBlock->get(SuperBlockData::BLOCK_SIZE)];
	for (unsigned int i=0;i<_superBlock->get(SuperBlockData::BLOCK_SIZE);i++) {
		zerroBlock[i]=0;
	}
	_da->write(id,zerroBlock);

	delete zerroBlock;*/
}

INodeType LowLevelLayer::getInodeType(INodeId iNodeId){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	char type(0);
	_da->seekAndRead(iNodeId+3,sizeof(char),0,&type,sizeof(char));
	// 	if (type==0){
	// 		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
	// 		throw Exception("Inode not initialyzed...");
	// 	}
	INodeType res(NULL_TYPE);
	switch(type){
		case 0:
			res=NULL_TYPE;
			break;
		case 1:
			res=FILE_TYPE;
			break;
		case 2:
			res=DIRECTORY_TYPE;
			break;
		case 3:
			res=SOFT_LINK_TYPE;
			break;
	}
	return res;
}

void LowLevelLayer::setInodeType(INodeId iNodeId, INodeType type){
	if (type==NULL_TYPE){
		cerr<<LowLevelLayer::ILLEGAL_INODE_TYPE;
		throw Exception("Illegal iNode type.");
	}
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	if (getInodeType(iNodeId)==NULL_TYPE){
		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
		throw Exception("Inode not initialyzed...");
	}
	char charType(type);
	_da->seekAndWrite(iNodeId+3,sizeof(char),0,&charType,sizeof(char));
}

BlockId LowLevelLayer::getDataBlock(INodeId iNodeId, unsigned int offset){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	if (getInodeType(iNodeId)==NULL_TYPE){
		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
		throw Exception("Inode not initialyzed...");
	}
	if (offset<0 || offset>=10+(_da->getBlockSize()/sizeof(unsigned int))){
		cerr<<LowLevelLayer::INODE_BLOCK_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inode block index out of bounds");
	}
	BlockId res(0);
	if (offset<10){
		_da->seekAndRead(iNodeId+3,sizeof(char),6+offset*sizeof(unsigned int),&res,sizeof(unsigned int));
	} else {
		BlockId indirectBlockId(0);
		_da->seekAndRead(iNodeId+3,sizeof(char),46,&indirectBlockId,sizeof(unsigned int));
		_da->seekAndRead(indirectBlockId,sizeof(unsigned int),offset-10,&res,sizeof(unsigned int));
	}
	return res;
}

//TODO check if data block points to another inode and increment its hardlinks count
void LowLevelLayer::setDataBlock(INodeId iNodeId, unsigned int offset, BlockId blockId){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	if (getInodeType(iNodeId)==NULL_TYPE){
		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
		throw Exception("Inode not initialyzed...");
	}
	if (offset<0 || offset>=10+(_da->getBlockSize()/sizeof(unsigned int))){
		cerr<<LowLevelLayer::INODE_BLOCK_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inode block index out of bounds");
	}
	if (offset<10){
		_da->seekAndWrite(iNodeId+3,sizeof(char),6+offset*sizeof(unsigned int),&blockId,sizeof(unsigned int));
	} else {
		BlockId indirectBlockId(0);
		_da->seekAndRead(iNodeId+3,sizeof(char),46,&indirectBlockId,sizeof(unsigned int));
		_da->seekAndWrite(indirectBlockId,sizeof(unsigned int),offset-10,&blockId,sizeof(unsigned int));
	}
}

unsigned int LowLevelLayer::getFileSize(INodeId iNodeId){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	if (getInodeType(iNodeId)==NULL_TYPE){
		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
		throw Exception("Inode not initialyzed...");
	}
	unsigned int res(0);
	_da->seekAndRead(iNodeId+3,sizeof(char),2,&res,sizeof(unsigned int));
	return res;
}

void LowLevelLayer::setFileSize(INodeId iNodeId, unsigned int fileSize){
	unsigned int inodesArrSize(_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE));
	if (iNodeId<0 || iNodeId>=inodesArrSize){
		cerr<<LowLevelLayer::INODE_INDEX_OUT_OF_BOUNDS;
		throw Exception("Inodes index out of bounds");
	}
	if (getInodeType(iNodeId)==NULL_TYPE){
		cerr<<LowLevelLayer::INODE_NOT_INITIALIZED;
		throw Exception("Inode not initialyzed...");
	}
	if(fileSize<0 || fileSize>26*_da->getBlockSize()){
		cerr<<LowLevelLayer::INODE_ILLEGAL_FILE_SIZE;
		throw Exception("Illegal iNode file size");
	}
	_da->seekAndWrite(iNodeId+3,sizeof(char),2,&fileSize,sizeof(unsigned int));
}


//Block Control
BlockId LowLevelLayer::allocateDataBlock(){
	BlockId res(0);
	unsigned int freeBlocksCount(_superBlock->get(SuperBlockData::FREE_BLOCKS_COUNT));
	if(freeBlocksCount<=0){
		cerr<<LowLevelLayer::NO_FREE_BLOCKS;
		throw Exception("No free blocks to allocate");
	}
	unsigned int adressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	unsigned int firstFreeBlocksBlock(_superBlock->get(SuperBlockData::FIRST_FREE_BLOCKS_BLOCK));
	unsigned int firstFreeBlocksOffset(_superBlock->get(SuperBlockData::FIRST_FREE_BLOCK_OFFSET));
	//remove block id from list
	_da->seekAndRead(firstFreeBlocksBlock,sizeof(unsigned int),firstFreeBlocksOffset,&res,sizeof(unsigned int));
	unsigned int temp=0;
	_da->seekAndWrite(firstFreeBlocksBlock,sizeof(unsigned int),firstFreeBlocksOffset,&temp,sizeof(unsigned int));
	//set new offset
	unsigned int newOffset((firstFreeBlocksOffset+1)%adressesPerBlock);
	_superBlock->set(SuperBlockData::FIRST_FREE_BLOCK_OFFSET,newOffset);
	//set new block (if needed)
	if (newOffset==0){
		unsigned int newBlock(0);
		_da->seekAndRead(firstFreeBlocksBlock,sizeof(unsigned int),firstFreeBlocksOffset+1,&newBlock,sizeof(unsigned int));
		_superBlock->set(SuperBlockData::FIRST_FREE_BLOCKS_BLOCK,newBlock);
	}
	_superBlock->set(SuperBlockData::FREE_BLOCKS_COUNT,freeBlocksCount-1);
	char* zerroBlock = new char[_superBlock->get(SuperBlockData::BLOCK_SIZE)];
	for (unsigned int i=0;i<_superBlock->get(SuperBlockData::BLOCK_SIZE);i++) {
		zerroBlock[i]=0;
	}
	_da->write(res,zerroBlock);
	delete zerroBlock;
	return res;
}

/* Assumes the data block is allocated*/
void LowLevelLayer::freeDataBlock(BlockId id){
	if (id<3+_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE)+_superBlock->getExtraFreeBlocksBlocksCount()+_superBlock->getExtraFreeInodesBlocksCount()
		|| id>=_superBlock->get(SuperBlockData::DISK_SIZE)/_superBlock->get(SuperBlockData::BLOCK_SIZE)){
		cerr<<LowLevelLayer::BLOCKS_INDEX_OUT_OF_BOUNDS;
		throw Exception("Blocks index out of bounds");
	}
	unsigned int adressesPerBlock(_da->getBlockSize()/sizeof(unsigned int)-1);
	unsigned int lastFreeBlocksId(_superBlock->get(SuperBlockData::LAST_FREE_BLOCKS_BLOCK));
	unsigned int lastFreeBlocksOffset(_superBlock->get(SuperBlockData::LAST_FREE_BLOCK_OFFSET));
	//calculate correct block and offset to add
	unsigned int newOffset((lastFreeBlocksOffset+1)%adressesPerBlock);
	_superBlock->set(SuperBlockData::LAST_FREE_BLOCK_OFFSET,newOffset);
	if (newOffset==0){
		_da->seekAndRead(lastFreeBlocksId,sizeof(unsigned int),lastFreeBlocksOffset+1,&lastFreeBlocksId,sizeof(unsigned int));
		_superBlock->set(SuperBlockData::LAST_FREE_BLOCKS_BLOCK,lastFreeBlocksId);
	}
	//write just free block id to list
	_da->seekAndWrite(lastFreeBlocksId,sizeof(unsigned int),newOffset,&id,sizeof(unsigned int));
	unsigned int freeBlocksCount(_superBlock->get(SuperBlockData::FREE_BLOCKS_COUNT));
	_superBlock->set(SuperBlockData::FREE_BLOCKS_COUNT,freeBlocksCount+1);
	/*//erase data from block
	char* zerroBlock = new char[_superBlock->get(SuperBlockData::BLOCK_SIZE)];
	for (unsigned int i=0;i<_superBlock->get(SuperBlockData::BLOCK_SIZE);i++) {
		zerroBlock[i]=0;
	}
	_da->write(id,zerroBlock);

	delete zerroBlock;*/
}

void LowLevelLayer::readBlock(BlockId id, char* buffer){
	_da->read(id,sizeof(char),buffer);
}

void LowLevelLayer::writeBlock(BlockId id, char* newData){
	_da->write(id,newData);
}

unsigned int LowLevelLayer::getBlockSize(){
	return _da->getBlockSize();
}

/*Each i-node has the following structure:
File type: file, directory, soft-link. (1 byte)
Number of hard links. (1 byte)
File size in bytes. (4 bytes)
10 direct blocks. (10*4 bytes)
1 single-indirect block. (4 bytes)*/
void LowLevelLayer::initNewInode(INodeId id){
	if (id<0 && id>=_superBlock->get(SuperBlockData::INODE_ARRAY_SIZE)){
		throw Exception("Index out of bounds exception");
	}
	char fileType(FILE_TYPE);
	_da->seekAndWrite(id,sizeof(char),0,&fileType,sizeof(char));
	char hardLinksCount(0);
	_da->seekAndWrite(id,sizeof(char),1,&hardLinksCount,sizeof(char));
	unsigned int fileSize(0);
	_da->seekAndWrite(id,sizeof(char),2,&fileSize,sizeof(unsigned int));
	unsigned int directBlocks[10];
	for (int i=0;i<10;i++) {
		directBlocks[i]=0;
	}
	_da->seekAndWrite(id,sizeof(char),3,&directBlocks,10*sizeof(unsigned int));
	unsigned int indirectBlock(allocateDataBlock());
	_da->seekAndWrite(id,sizeof(char),46,&indirectBlock,sizeof(unsigned int));
}

void LowLevelLayer::freeINode(INodeId id){
	unsigned int directBlocks[10];
	_da->seekAndRead(id,sizeof(char),3,&directBlocks,10*sizeof(unsigned int));
	for (int i=0;i<10;i++) {
		if (directBlocks[i]!=0){
			freeDataBlock(directBlocks[i]);
		}
	}
	unsigned int indirectBlock(0);
	_da->seekAndRead(id,sizeof(char),46,&indirectBlock,sizeof(unsigned int));
	unsigned int addressesPerBlock(_da->getBlockSize()/sizeof(unsigned int));
	unsigned int* indirectBlocks=new unsigned int[addressesPerBlock];
	_da->read(indirectBlock,sizeof(unsigned int),indirectBlocks);
	for (unsigned int i=0;i<addressesPerBlock;i++) {
		if (indirectBlocks[i]!=0){
			freeDataBlock(indirectBlocks[i]);
		}
	}
	delete indirectBlocks;
	char fileType(NULL_TYPE);
	_da->seekAndWrite(id,sizeof(char),0,&fileType,sizeof(char));
	//TODO check what to do with hardlinks
}


INodeId LowLevelLayer::getRootINode(){
	return _superBlock->get(SuperBlockData::ROOT_INODE);
}